Skip to content

Conversation

@chris-1243
Copy link
Contributor

@chris-1243 chris-1243 commented Nov 20, 2025

From discussion

Koenkk/zigbee2mqtt#29257

I hope I have done it the right way.

@Koenkk Koenkk merged commit a88bb18 into Koenkk:master Nov 20, 2025
3 checks passed
@Koenkk
Copy link
Owner

Koenkk commented Nov 20, 2025

Thanks!

@andrei-lazarov
Copy link
Contributor

andrei-lazarov commented Dec 18, 2025

Hi @chris-1243, can you revisit this?

I bought some more TRETAKT plugs and I had to peek through the converters when I was missing the child_lock and led_enable features.

I noticed you only exposed them for the latest version (2.4.25), but I was curious if it also works without updating.

I copy-pasted the code into an external converter: ikea_plug.js

They work on the initial version (2.4.4) too:

  • child_lock works the same
  • led_enable is inverted (so I would call it led_disable here) and a little different
    • OFF
      • led OFF when plug OFF
      • led ON when plug ON
    • ON
      • led OFF when plug OFF
      • led ON for 10 seconds after plug is turned ON, then OFF

Also, with the current implementation, would the settings disappear after a future update (version no longer matches)?

@chris-1243
Copy link
Contributor Author

chris-1243 commented Dec 18, 2025

@andrei-lazarov In order to not break devices with older firmware, this modification works on version 2.4.25 or higher. I do not have a plug with an older firmware to test with.

The CHILD_LOCK option is easy to modify. It will take more time to modify LED_ENABLE to have this option working correctly on both version.

Thanks to have tested

Also, with the current implementation, would the settings disappear after a future update (version no longer matches)?

The implementation works on firmware version 2.4.25 or higher. Let's hope IKEA does not modify those parameters in a future firmware...

@chris-1243
Copy link
Contributor Author

chris-1243 commented Dec 19, 2025

@andrei-lazarov Could you test this converter ? It should do the job. Not the best way to write it but I still need to figure out how javascript works... Import it as a *.mjs file.

import {Zcl} from "zigbee-herdsman";
import {gte as semverGte, valid as semverValid} from "semver";

import {access, binary} from "zigbee-herdsman-converters/lib/exposes";
import * as m from "zigbee-herdsman-converters/lib/modernExtend";
import {addCustomClusterManuSpecificIkeaUnknown} from "zigbee-herdsman-converters/lib/ikea";
import {isDummyDevice} from "zigbee-herdsman-converters/lib/utils";

export const manufacturerOptions = {manufacturerCode: Zcl.ManufacturerCode.IKEA_OF_SWEDEN};

function addCustomClusterManuSpecificIkeaSmartPlug() {
    return m.deviceAddCustomCluster("manuSpecificIkeaSmartPlug", {
        ID: 0xfc85,
        manufacturerCode: Zcl.ManufacturerCode.IKEA_OF_SWEDEN,
        attributes: {
            childLock: {ID: 0x0000, type: Zcl.DataType.BOOLEAN},
            ledEnable: {ID: 0x0001, type: Zcl.DataType.BOOLEAN},
        },
        commands: {},
        commandsResponse: {},
    });
}

const ikeaModernExtend = {
    smartPlugChildLock: (args) => {
        const resultName = "child_lock";
        const resultDescription = "Enables/disables physical input on the device.";

        const result = m.binary({
            name: resultName,
            cluster: "manuSpecificIkeaSmartPlug",
            attribute: {ID: 0x0000, type: Zcl.DataType.BOOLEAN},
            entityCategory: "config",
            valueOff: ["UNLOCK", 0x00],
            valueOn: ["LOCK", 0x01],
            description: resultDescription,
            zigbeeCommandOptions: {manufacturerCode: Zcl.ManufacturerCode.IKEA_OF_SWEDEN},
        });

        result.exposes = [
            (device) => {
                if (
                    !isDummyDevice(device) &&
                    device.softwareBuildID &&
                    semverValid(device.softwareBuildID) &&
                    semverGte(device.softwareBuildID, "2.4.4")
                ) {
                    return [binary(resultName, access.ALL, "LOCK", "UNLOCK").withDescription(resultDescription).withCategory("config")];
                }
                return [];
            },
        ];

        return result;
    },

    smartPlugLedEnable: (args) => {
        const resultName = "led_enable";
        const resultDescription = "Enables/disables the led on the device.";

        const result = m.binary({
            name: resultName,
            cluster: "manuSpecificIkeaSmartPlug",
            attribute: {ID: 0x0001, type: Zcl.DataType.BOOLEAN},
            entityCategory: "config",
            valueOff: ["FALSE", 0x00],
            valueOn: ["TRUE", 0x01],
            description: resultDescription,
            zigbeeCommandOptions: {manufacturerCode: Zcl.ManufacturerCode.IKEA_OF_SWEDEN},
        });

        result.exposes = [
            (device) => {
                if (
                    !isDummyDevice(device) &&
                    device.softwareBuildID &&
                    semverValid(device.softwareBuildID) &&
                    semverGte(device.softwareBuildID, "2.4.25")
                ) {
                    return [binary(resultName, access.ALL, "TRUE", "FALSE").withDescription(resultDescription).withCategory("config")];
                }
                return [];
            },
        ];

        return result;
    },

    smartPlugLedEnableTretakt: (args) => {
        const resultName = "led_enable";
        const resultDescription = "Enables/disables the led on the device (When set to FALSE, the led will turn off after 10 seconds).";

        const result = m.binary({
            name: resultName,
            cluster: "manuSpecificIkeaSmartPlug",
            attribute: {ID: 0x0001, type: Zcl.DataType.BOOLEAN},
            entityCategory: "config",
            valueOff: ["TRUE", 0x00],
            valueOn: ["FALSE", 0x01],
            description: resultDescription,
            zigbeeCommandOptions: {manufacturerCode: Zcl.ManufacturerCode.IKEA_OF_SWEDEN},
        });

        result.exposes = [
            (device) => {
                if (
                    !isDummyDevice(device) &&
                    device.softwareBuildID &&
                    semverValid(device.softwareBuildID) &&
                    device.softwareBuildID === "2.4.4"
                ) {
                    return [binary(resultName, access.ALL, "TRUE", "FALSE").withDescription(resultDescription).withCategory("config")];
                }
                return [];
            },
        ];

        return result;
    },
};

export default {
    zigbeeModel: ["TRETAKT Smart plug"],
    model: "E22x4",
    vendor: "IKEA",
    description: "TRETAKT smart plug",
    whiteLabel: [
        {model: "E2204", vendor: "IKEA", description: "TRETAKT smart plug (EU)"},
        {model: "E2214", vendor: "IKEA", description: "TRETAKT smart plug (CH)"},
    ],
    extend: [
        addCustomClusterManuSpecificIkeaSmartPlug(),
        addCustomClusterManuSpecificIkeaUnknown(),
        m.onOff(),
        ikeaModernExtend.smartPlugChildLock(),
        ikeaModernExtend.smartPlugLedEnable(),
        ikeaModernExtend.smartPlugLedEnableTretakt(),
        m.identify(),
    ],
    ota: true,
};

@Koenkk I know you are busy. Would you have any idea how I could merge smartPlugLedEnable and smartPlugLedEnableTretakt together. I am able to set the two binary functions but I am stuck how to dynamically expose those two binaries based on the firmware version.

The result.exposes function drives me crazy

@Koenkk
Copy link
Owner

Koenkk commented Dec 19, 2025

I think it's the nicest to extend

export function binary<Cl extends string | number, Custom extends TCustomCluster | undefined = undefined>(
with a function e.g. shouldExpose(device), based on that we can make it conditional and keep the m.binary construction.

@chris-1243
Copy link
Contributor Author

Thanks.

Let's see how far I will be able to go.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants